﻿namespace jQube.Web
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Text;
    using System.Xml.Xsl;
    using System.Xml;

    internal class ParserStringWriter : StringWriter
    {
        private const string ParseableUpdatePanelAttributeName = "parseableControl=\"UpdatePanel\"";

        private bool formStarted = false;

        private string jsonParsedHiddenFields = string.Empty;
        private string jsonParsedScripts = string.Empty;
        private string jsonParsedUpdatePanels = string.Empty;

        public ParserStringWriter()
            : base(CultureInfo.CurrentCulture)
        {
        }

        public string JSonParsedHiddenFields
        {
            get
            {
                return this.jsonParsedHiddenFields;
            }
        }

        public string JSonParsedScripts
        {
            get
            {
                return this.jsonParsedScripts;
            }
        }

        public string JSonParsedUpdatePanels
        {
            get
            {
                return this.jsonParsedUpdatePanels;
            }
        }

        public override void Write(string value)
        {
            if (this.formStarted && value == "form")
            {
                base.Write(value);

                string htmlFormTextOutput = string.Format("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n{0}>", this.ToString());

                this.SetJSonParsedHiddenFields(htmlFormTextOutput);
                this.SetJSonParserScripts(htmlFormTextOutput);
                this.SetJSonParsedUpdatePanels(htmlFormTextOutput);

                return;
            }

            if (value == "form")
            {
                this.formStarted = true;
            }

            base.Write(value);
        }

        private void SetJSonParsedHiddenFields(string htmlFormTextOutput)
        {
            #region xslTransformFormHiddenFields
            string xslTransformFormHiddenFields = @"<?xml version=""1.0"" encoding=""utf-8""?>
<xsl:stylesheet version=""1.0"" xmlns:xsl=""http://www.w3.org/1999/XSL/Transform""
    xmlns:msxsl=""urn:schemas-microsoft-com:xslt"" exclude-result-prefixes=""msxsl"">
  <xsl:output method=""text"" encoding=""utf-8"" omit-xml-declaration=""yes"" indent=""no""/>

  <xsl:template match=""/"">
    <xsl:variable name=""quote"">""</xsl:variable>
    <xsl:for-each select=""//div[@class='aspNetHidden']"">
      <xsl:for-each select=""input[@type='hidden']"">
        <xsl:value-of select=""concat('{', $quote, 'name', $quote, ':', $quote, @id, $quote, ',', $quote, 'value', $quote, ':' , $quote, @value, $quote, '}')""/>
      </xsl:for-each>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>";
            #endregion

            StringWriter jsonParsedText = new StringWriter();

            XslCompiledTransform xslCompiledTrasnform = new XslCompiledTransform();

            xslCompiledTrasnform.Load(new XmlTextReader(new StringReader(xslTransformFormHiddenFields)));

            xslCompiledTrasnform.Transform(
                new XmlTextReader(new StringReader(htmlFormTextOutput)),
                new XmlTextWriter(jsonParsedText));

            this.jsonParsedHiddenFields = jsonParsedText.ToString().Replace("}{", "},{");
        }

        private void SetJSonParserScripts(string htmlFormTextOutput)
        {
            #region xlsTransformFormScripts
            string xlsTransformFormScripts = @"<?xml version=""1.0"" encoding=""utf-8""?>
<xsl:stylesheet version=""1.0"" xmlns:xsl=""http://www.w3.org/1999/XSL/Transform""
    xmlns:msxsl=""urn:schemas-microsoft-com:xslt"" exclude-result-prefixes=""msxsl"">
  <xsl:output method=""text"" encoding=""utf-8"" omit-xml-declaration=""yes"" indent=""no"" cdata-section-elements=""script""/>
  <xsl:template match=""/"">
    <xsl:variable name=""q"">""</xsl:variable>
    <xsl:for-each select=""//script"">
      <xsl:choose>
        <xsl:when test=""@src != ''"">
          <xsl:value-of select=""concat('{', $q, 'script', $q, ':{', $q, 'src', $q, ':', $q, @src, $q, ',', $q,'type', $q, ':', $q, @type, $q, '}}')""/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:variable name=""text"">
            <xsl:call-template name=""replace"">
              <xsl:with-param name=""text"" select="".""/>
              <xsl:with-param name=""replace"">
                <xsl:value-of select=""$q""/>
              </xsl:with-param>
              <xsl:with-param name=""by"">
                <xsl:value-of select=""concat('\', $q)""/>
              </xsl:with-param>
            </xsl:call-template>
          </xsl:variable>
          <xsl:value-of select=""concat('{', $q, 'script', $q, ':{', $q, 'text', $q, ':', $q, $text, $q, ',', $q,'type', $q, ':', $q, @type, $q, ',', $q, 'language', $q, ':', $q, @language, $q, '}}')""/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
  </xsl:template>
  <xsl:template name=""replace"">
    <xsl:param name=""text"" />
    <xsl:param name=""replace"" />
    <xsl:param name=""by"" />
    <xsl:choose>
      <xsl:when test=""contains($text, $replace)"">
        <xsl:value-of select=""substring-before($text,$replace)"" />
        <xsl:value-of select=""$by"" />
        <xsl:call-template name=""replace"">
          <xsl:with-param name=""text""
          select=""substring-after($text,$replace)"" />
          <xsl:with-param name=""replace"" select=""$replace"" />
          <xsl:with-param name=""by"" select=""$by"" />
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select=""$text"" />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>";

            #endregion

            htmlFormTextOutput = htmlFormTextOutput
                .Replace("//<![CDATA[", "//&lt;![CDATA[")
                .Replace("//]]>", "//]]&gt;");

            StringWriter jsonParsedText = new StringWriter();

            XslCompiledTransform xslCompiledTrasnform = new XslCompiledTransform();

            xslCompiledTrasnform.Load(new XmlTextReader(new StringReader(xlsTransformFormScripts)));

            xslCompiledTrasnform.Transform(
                new XmlTextReader(new StringReader(htmlFormTextOutput)),
                new XmlTextWriter(jsonParsedText));

            this.jsonParsedScripts = jsonParsedText.ToString().Trim()
                .Replace("}{", "},{")
                .Replace("&lt;", "<")
                .Replace("&gt;", ">")
                .Replace("\r\n", "\\r\\n")
                .Replace("\t", "\\t");
        }

        private void SetJSonParsedUpdatePanels(string htmlFormTextOutput)
        {
            this.EncapsulateUpdatePanelsContent(ref htmlFormTextOutput);

            #region xslTransformUpdatePanels

            string xslTransformUpdatePanels = @"<?xml version=""1.0"" encoding=""utf-8""?>
<xsl:stylesheet version=""1.0"" xmlns:xsl=""http://www.w3.org/1999/XSL/Transform""
    xmlns:msxsl=""urn:schemas-microsoft-com:xslt"" exclude-result-prefixes=""msxsl"">
  <xsl:output method=""text"" encoding=""utf-8"" omit-xml-declaration=""yes"" indent=""no"" cdata-section-elements=""script""/>
  <xsl:template match=""/"">
    <xsl:variable name=""q"">""</xsl:variable>
    <xsl:for-each select=""//div[@parseableControl = 'UpdatePanel']"">
      <xsl:variable name=""text"">
        <xsl:call-template name=""replace"">
          <xsl:with-param name=""text"" select="".""/>
          <xsl:with-param name=""replace"">
            <xsl:value-of select=""$q""/>
          </xsl:with-param>
          <xsl:with-param name=""by"">
            <xsl:value-of select=""concat('\', $q)""/>
          </xsl:with-param>
        </xsl:call-template>
      </xsl:variable>
      <xsl:value-of select=""concat('{', $q, 'updatePanel', $q, ':{', $q, 'name', $q, ':', $q, @id, $q, ',', $q, 'html', $q, ':', $q, $text, $q, '}}')""/>
    </xsl:for-each>
  </xsl:template>
  <xsl:template name=""replace"">
    <xsl:param name=""text"" />
    <xsl:param name=""replace"" />
    <xsl:param name=""by"" />
    <xsl:choose>
      <xsl:when test=""contains($text, $replace)"">
        <xsl:value-of select=""substring-before($text,$replace)"" />
        <xsl:value-of select=""$by"" />
        <xsl:call-template name=""replace"">
          <xsl:with-param name=""text""
          select=""substring-after($text,$replace)"" />
          <xsl:with-param name=""replace"" select=""$replace"" />
          <xsl:with-param name=""by"" select=""$by"" />
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select=""$text"" />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>";

            #endregion

            StringWriter jsonParsedText = new StringWriter();

            XslCompiledTransform xslCompiledTrasnform = new XslCompiledTransform();

            xslCompiledTrasnform.Load(new XmlTextReader(new StringReader(xslTransformUpdatePanels)));

            xslCompiledTrasnform.Transform(
                new XmlTextReader(new StringReader(htmlFormTextOutput)),
                new XmlTextWriter(jsonParsedText));

            this.jsonParsedUpdatePanels = jsonParsedText.ToString().Trim()
                .Replace("}{", "},{")
                .Replace("&lt;", "<")
                .Replace("&gt;", ">")
                .Replace("\r\n", "\\r\\n")
                .Replace("\t", "\\t");
        }

        private void EncapsulateUpdatePanelsContent(ref string htmlFormTextOutput)
        {
            int startIndex = htmlFormTextOutput.IndexOf(ParseableUpdatePanelAttributeName);
            int endIndex = 0;

            while (startIndex > 0)
            {
                string htmlUpdatePanel = htmlFormTextOutput.Substring(0, startIndex);

                startIndex = htmlUpdatePanel.LastIndexOf("<");
                endIndex = htmlFormTextOutput.IndexOf("</div>", startIndex);

                htmlUpdatePanel = htmlFormTextOutput.Substring(startIndex, (endIndex - startIndex) + 6);

                while (!this.ValidateXml(htmlUpdatePanel))
                {
                    endIndex = htmlFormTextOutput.IndexOf("</div>", endIndex + 6);
                    htmlUpdatePanel = htmlFormTextOutput.Substring(startIndex, (endIndex - startIndex) + 6);
                }

                string htmlUpdatePanelEncapsulated = string.Format("{0}<![CDATA[{1}]]>{2}",
                    htmlUpdatePanel.Substring(0, htmlUpdatePanel.IndexOf(">") + 1),
                    htmlUpdatePanel.Substring(htmlUpdatePanel.IndexOf(">") + 1,
                        htmlUpdatePanel.LastIndexOf("</div>") - (htmlUpdatePanel.IndexOf(">") + 1)),
                        "</div>");

                htmlFormTextOutput = htmlFormTextOutput.Replace(htmlUpdatePanel, htmlUpdatePanelEncapsulated);

                startIndex = htmlFormTextOutput.IndexOf(ParseableUpdatePanelAttributeName, endIndex + 6);
            }
        }

        private bool ValidateXml(string value)
        {
            try
            {
                XmlReaderSettings readerSettings = new XmlReaderSettings();
                readerSettings.ConformanceLevel = ConformanceLevel.Document;
                readerSettings.CheckCharacters = true;
                readerSettings.ValidationType = ValidationType.None;

                XmlReader xmlReader = XmlReader.Create(new StringReader(value));

                XmlDocument xdoc = new XmlDocument();
                xdoc.Load(xmlReader);

                return true;
            }
            catch
            {
                return false;
            }
        }
    }
}
